package com.ruge.tool.date;

import com.ruge.tool.coll.CollTool;
import com.ruge.tool.enums.DateEnum;
import com.ruge.tool.enums.DateFormatEnum;
import com.ruge.tool.enums.OrderEnum;
import com.ruge.tool.str.StringTool;
import lombok.SneakyThrows;
import org.apache.commons.lang3.time.DateUtils;

import java.text.SimpleDateFormat;
import java.time.*;
import java.time.format.DateTimeFormatter;
import java.time.temporal.TemporalAdjusters;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.List;

/**
 * 日期工具类
 *
 * @author ruge.wu
 * @since 2021/6/3 20:17
 **/
public class DateTool {

    /**
     * 获取date
     *
     * @return {@link Date}
     */
    public static Date getDate() {
        return getDate(null);
    }

    /**
     * 获取date数据
     *
     * @param obj obj
     * @return {@link Date}
     */
    public static Date getDate(Object obj) {
        if (null == obj) {
            return new Date();
        }
        if (obj instanceof Long) {
            Long time = (Long) obj;
            return new Date(time);
        } else if (obj instanceof LocalDateTime) {
            LocalDateTime time = (LocalDateTime) obj;
            return Date.from(time.atZone(ZoneId.systemDefault()).toInstant());
        } else if (obj instanceof Date) {
            return (Date) obj;
        }
        throw new RuntimeException("getDate 方法异常 入参:" + obj);
    }

    /**
     * 获取 LocalDateTime
     *
     * @return {@link LocalDateTime}
     */
    public static LocalDateTime getLocalDateTime() {
        return getLocalDateTime(null);
    }

    /**
     * 获取 LocalDateTime
     *
     * @param obj obj
     * @return {@link LocalDateTime}
     */
    public static LocalDateTime getLocalDateTime(Object obj) {
        if (null == obj) {
            return LocalDateTime.now();
        }
        if (obj instanceof Long) {
            Long time = (Long) obj;
            return LocalDateTime.ofInstant(Instant.ofEpochMilli(time), ZoneId.systemDefault());
        } else if (obj instanceof Date) {
            Date time = (Date) obj;
            return LocalDateTime.ofInstant(time.toInstant(), ZoneId.systemDefault());
        } else if (obj instanceof LocalDateTime) {
            return (LocalDateTime) obj;
        }
        throw new RuntimeException("getLocalDateTime 方法异常 入参:" + obj);
    }

    /**
     * 获取时间戳
     *
     * @return 时间戳
     */
    public static Long getTimestamp() {
        return getTimestamp(null);
    }

    /**
     * 获取时间戳
     *
     * @param obj obj
     * @return 时间戳
     */
    public static Long getTimestamp(Object obj) {
        if (null == obj) {
            return System.currentTimeMillis();
        }
        if (obj instanceof Long) {
            return (Long) obj;
        } else if (obj instanceof Date) {
            Date time = (Date) obj;
            return time.getTime();
        } else if (obj instanceof LocalDateTime) {
            LocalDateTime time = (LocalDateTime) obj;
            return time.toInstant(ZoneOffset.ofHours(8)).toEpochMilli();
        }
        throw new RuntimeException("getTimestamp 方法异常 入参:" + obj);
    }

    /**
     * 返回年份变动后的 {@link Date}
     *
     * @param year 变动的年份，参数小于0为减法
     * @return {@link Date}
     */
    public static Date addYear(int year) {
        return addYear(null, year);
    }

    /**
     * 返回年份变动后的 {@link Date}
     *
     * @param obj  当前时间
     * @param year 变动的年份，参数小于0为减法
     * @return {@link Date}
     */
    public static Date addYear(Object obj, int year) {
        if (null == obj) {
            return DateUtils.addYears(new Date(), year);
        }
        if (obj instanceof Long) {
            Long time = (Long) obj;
            return DateUtils.addYears(new Date(time), year);
        } else if (obj instanceof Date) {
            Date time = (Date) obj;
            return DateUtils.addYears(time, year);
        }
        throw new RuntimeException("addYear 方法异常 入参:" + obj);
    }


    /**
     * 返回月份变动后的 {@link Date}
     *
     * @param month 变动的月数，参数小于0为减法
     * @return 时间戳
     */
    public static Date addMonth(int month) {
        return addMonth(null, month);
    }

    /**
     * 返回月份变动后的 {@link Date}
     *
     * @param obj   当前时间
     * @param month 变动的月数，参数小于0为减法
     * @return 时间戳
     */
    public static Date addMonth(Object obj, int month) {
        if (null == obj) {
            return DateUtils.addMonths(new Date(), month);
        }
        if (obj instanceof Long) {
            Long time = (Long) obj;
            return DateUtils.addMonths(new Date(time), month);
        } else if (obj instanceof Date) {
            Date time = (Date) obj;
            return DateUtils.addMonths(time, month);
        }
        throw new RuntimeException("addMonth 方法异常 入参:" + obj);
    }

    /**
     * 日期变动后的 {@link Date}
     *
     * @param day 变动的天数，参数小于0为减法
     * @return 时间戳
     */
    public static Date addDay(int day) {
        return addDay(null, day);
    }


    /**
     * 日期变动后的 {@link Date}
     *
     * @param obj 当前时间
     * @param day 变动的天数，参数小于0为减法
     * @return 时间戳
     */
    public static Date addDay(Object obj, int day) {
        if (null == obj) {
            return DateUtils.addDays(new Date(), day);
        }
        if (obj instanceof Long) {
            Long time = (Long) obj;
            return DateUtils.addDays(new Date(time), day);
        } else if (obj instanceof Date) {
            Date time = (Date) obj;
            return DateUtils.addDays(time, day);
        }
        throw new RuntimeException("addDay 方法异常 入参:" + obj);
    }

    /**
     * 小时变动后的 {@link Date}
     *
     * @param hour 变动的小时，参数小于0为减法
     * @return 时间戳
     */
    public static Date addHour(int hour) {
        return addHour(null, hour);
    }


    /**
     * 小时变动后的 {@link Date}
     *
     * @param obj  当前时间
     * @param hour 变动的小时，参数小于0为减法
     * @return 时间戳
     */
    public static Date addHour(Object obj, int hour) {
        if (null == obj) {
            return DateUtils.addHours(new Date(), hour);
        }
        if (obj instanceof Long) {
            Long time = (Long) obj;
            return DateUtils.addHours(new Date(time), hour);
        } else if (obj instanceof Date) {
            Date time = (Date) obj;
            return DateUtils.addHours(time, hour);
        }
        throw new RuntimeException("addHour 方法异常 入参:" + obj);
    }

    /**
     * 分钟变动后的 {@link Date}
     *
     * @param minutes 变动的分钟，参数小于0为减法
     * @return 时间戳
     */
    public static Date addMinute(int minutes) {
        return addMinute(null, minutes);
    }

    /**
     * 分钟变动后的 {@link Date}
     *
     * @param obj     当前时间
     * @param minutes 变动的分钟，参数小于0为减法
     * @return 时间戳
     */
    public static Date addMinute(Object obj, int minutes) {
        if (null == obj) {
            return DateUtils.addMinutes(new Date(), minutes);
        }
        if (obj instanceof Long) {
            Long time = (Long) obj;
            return DateUtils.addMinutes(new Date(time), minutes);
        } else if (obj instanceof Date) {
            Date time = (Date) obj;
            return DateUtils.addMinutes(time, minutes);
        }
        throw new RuntimeException("addMinute 方法异常 入参:" + obj);
    }

    /**
     * 秒钟变动后的 {@link Date}
     *
     * @param second 变动的秒钟，参数小于0为减法
     * @return 时间戳
     */
    public static Date addSecond(int second) {
        return addSecond(null, second);
    }

    /**
     * 秒钟变动后的 {@link Date}
     *
     * @param obj    当前时间
     * @param second 变动的秒钟，参数小于0为减法
     * @return 时间戳
     */
    public static Date addSecond(Object obj, int second) {
        if (null == obj) {
            return DateUtils.addSeconds(new Date(), second);
        }
        if (obj instanceof Long) {
            Long time = (Long) obj;
            return DateUtils.addSeconds(new Date(time), second);
        } else if (obj instanceof Date) {
            Date time = (Date) obj;
            return DateUtils.addSeconds(time, second);
        }
        throw new RuntimeException("addMinute 方法异常 入参:" + obj);
    }

    /**
     * 获取初始 {@link Date}
     *
     * @param obj      时间
     * @param dateEnum 获取初始化枚举
     * @return 时间戳
     */
    public static Date getBegin(Object obj, DateEnum dateEnum) {
        LocalDateTime localDateTime = getLocalDateTime(obj);
        switch (dateEnum) {
            case YEAR:
                localDateTime = LocalDateTime.of(localDateTime.toLocalDate(), LocalTime.MIN);
                localDateTime = localDateTime.with(TemporalAdjusters.firstDayOfYear());
                break;
            case MONTH:
                localDateTime = LocalDateTime.of(localDateTime.toLocalDate(), LocalTime.MIN);
                localDateTime = localDateTime.with(TemporalAdjusters.firstDayOfMonth());
                break;
            case DAY:
                localDateTime = localDateTime.withHour(0).withMinute(0).withSecond(0);
                break;
            case HOUR:
                localDateTime = localDateTime.withHour(0);
                break;
            case MINUTE:
                localDateTime = localDateTime.withMinute(0);
                break;
            case SECOND:
                localDateTime = localDateTime.withSecond(0);
                break;
            default:
                throw new RuntimeException("传递的枚举不识别 请重试");
        }
        return getDate(localDateTime);
    }

    /**
     * 获取一天的开始时间 {@link Date}
     *
     * @return 一天的开始时间
     */
    public static Date getDayBegin() {
        return getBegin(null, DateEnum.DAY);
    }

    /**
     * 获取一天的开始时间 {@link Date}
     *
     * @param date 时间
     * @return 一天的开始时间
     */
    public static Date getDayBegin(Object date) {
        return getBegin(date, DateEnum.DAY);
    }

    /**
     * 获取月初的时间 {@link Date}
     *
     * @return 月初的时间
     */
    public static Date getMonthBegin() {
        return getBegin(null, DateEnum.MONTH);
    }

    /**
     * 获取月初的时间 {@link Date}
     *
     * @param date 时间
     * @return 月初的时间
     */
    public static Date getMonthBegin(Object date) {
        return getBegin(date, DateEnum.MONTH);
    }

    /**
     * 获取最大时间戳
     *
     * @param date     时间戳
     * @param dateEnum 获取初始化枚举
     * @return 时间戳
     */
    public static Date getEnd(Object date, DateEnum dateEnum) {
        LocalDateTime localDateTime = getLocalDateTime(date);
        switch (dateEnum) {
            case YEAR:
                localDateTime = LocalDateTime.of(localDateTime.toLocalDate(), LocalTime.MAX);
                localDateTime = localDateTime.with(TemporalAdjusters.lastDayOfYear());
                break;
            case MONTH:
                localDateTime = LocalDateTime.of(localDateTime.toLocalDate(), LocalTime.MAX);
                localDateTime = localDateTime.with(TemporalAdjusters.lastDayOfMonth());
                break;
            case DAY:
                localDateTime = localDateTime.withHour(23).withMinute(59).withSecond(59);
                break;
            case HOUR:
                localDateTime = localDateTime.withHour(23);
                break;
            case MINUTE:
                localDateTime = localDateTime.withMinute(59);
                break;
            case SECOND:
                localDateTime = localDateTime.withSecond(59);
                break;
            default:
                throw new RuntimeException("传递的枚举不识别 请重试");
        }
        return getDate(localDateTime);
    }


    /**
     * 获取一天的结束时间
     *
     * @return 一天的结束时间
     */
    public static Date getDayEnd() {
        return getEnd(null, DateEnum.DAY);
    }

    /**
     * 获取一天的结束时间
     *
     * @param date 时间戳
     * @return 一天的结束时间
     */
    public static Date getDayEnd(Object date) {
        return getEnd(date, DateEnum.DAY);
    }

    /**
     * 获取月末的时间
     *
     * @return 月末的时间
     */
    public static Date getMonthEnd() {
        return getEnd(null, DateEnum.MONTH);
    }

    /**
     * 获取月末的时间
     *
     * @return 月末的时间
     */
    public static Date getMonthEnd(Object obj) {
        return getEnd(obj, DateEnum.MONTH);
    }

    /**
     * 获取两个时间戳之间的时间间隔集合
     *
     * @param startTime     开始日期(时间戳)
     * @param endTime       结束日期(时间戳)
     * @param startStopTime 最早开始时间(时间戳)
     * @param endStopTime   最晚结束时间(时间戳)
     * @param dateEnum      {@link DateEnum} 获取两个时间段之间的间隔类型
     * @param orderEnum     {@link OrderEnum} 默认ASC升序
     * @return 时间间隔集合
     */
    public static List<String> getBetween(Object startTime, Object endTime, Object startStopTime, Object endStopTime, DateEnum dateEnum, OrderEnum orderEnum) {
        List<String> list = new ArrayList<>();
        // startStopTime > startTime
        if (null != startStopTime && getDate(startStopTime).after(getDate(startTime))) {
            startTime = startStopTime;
        }
        // endStopTime < endTime
        if (null != endStopTime && getDate(endStopTime).before(getDate(endTime))) {
            endTime = endStopTime;
        }
        // startTime < endTime
        while (getDate(startTime).before(getDate(endTime))) {
            switch (dateEnum) {
                case YEAR:
                    list.add(new SimpleDateFormat("yyyy").format(getDate(startTime)));
                    startTime = getBegin(addYear(startTime, 1).getTime(), dateEnum);
                    break;
                case MONTH:
                    list.add(new SimpleDateFormat("yyyy-MM").format(getDate(startTime)));
                    startTime = getBegin(addMonth(startTime, 1).getTime(), dateEnum);
                    break;
                case DAY:
                    list.add(new SimpleDateFormat("yyyy-MM-dd").format(getDate(startTime)));
                    startTime = getBegin(addDay(startTime, 1).getTime(), dateEnum);
                    break;
                case HOUR:
                    list.add(new SimpleDateFormat("yyyy-MM-dd HH").format(getDate(startTime)));
                    startTime = addHour(startTime, 1);
                    break;
                case MINUTE:
                    list.add(new SimpleDateFormat("yyyy-MM-dd HH:mm").format(getDate(startTime)));
                    startTime = addMinute(startTime, 1);
                    break;
                case SECOND:
                    list.add(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(getDate(startTime)));
                    startTime = addSecond(startTime, 1);
                    break;
                default:
                    throw new RuntimeException("传递的枚举不识别 请重试");
            }
        }
        if (orderEnum == OrderEnum.DESC) {
            CollTool.reverse(list);
        }
        return list;
    }

    /**
     * 获取两个时间戳之间的日期间隔
     *
     * @param startTime 开始时间戳
     * @param endTime   结束时间戳
     * @return 两个时间戳之间的日期间隔
     */
    public static List<String> getBetween(Object startTime, Object endTime) {
        return getBetween(startTime, endTime, null, null, DateEnum.DAY, OrderEnum.ASC);
    }

    /**
     * 获取当日指定时间的时间戳
     *
     * @param date 当前时间的时间戳
     * @param hour 指定的小时
     * @return 指定时间的时间戳
     */
    public static Date getDayByHour(Object date, Integer hour) {
        LocalDateTime localDateTime = getLocalDateTime(date);
        LocalDateTime dateTime = LocalDateTime.of(localDateTime.toLocalDate(), LocalTime.of(hour, 0));
        return getDate(dateTime);

    }


    /**
     * 获取日期格式化实例
     *
     * @param date       时间
     * @param dateFormat {@link DateFormatEnum} 格式化枚举
     * @param format     自定义格式化
     * @return 日期格式化实例
     */
    public static String format(Object date, DateFormatEnum dateFormat, String format) {
        long timestamp;
        if (date instanceof Long) {
            timestamp = Long.parseLong(date.toString());
        } else if (date instanceof Date) {
            timestamp = ((Date) date).getTime();
        } else {
            timestamp = System.currentTimeMillis();
        }

        DateTimeFormatter dateFormatter = null;
        if (null != dateFormat) {
            dateFormatter = DateTimeFormatter.ofPattern(dateFormat.getDesc());
        }
        if (StringTool.isNotBlank(format)) {
            dateFormatter = DateTimeFormatter.ofPattern(format);
        }
        if (null == dateFormatter) {
            throw new RuntimeException("dateFormat 和 format 不能均为null");
        }
        return getLocalDateTime(timestamp).format(dateFormatter);
    }

    /**
     * 日期格式化
     *
     * @return yyyy-MM-dd
     */
    public static String formatDate() {
        return format(null, DateFormatEnum.DATE_MEDIUM, null);
    }

    /**
     * 日期格式化
     *
     * @param date 当前时间的时间戳
     * @return 格式化 yyyy-MM-dd
     */
    public static String format(Object date) {
        return format(date, DateFormatEnum.DATE_MEDIUM, null);
    }

    /**
     * 日期格式化
     *
     * @param pattern 格式
     * @return 格式化 yyyy-MM-dd
     */
    public static String format(String pattern) {
        return format(null, DateFormatEnum.DATE_MEDIUM, pattern);
    }


    /**
     * 日期格式化
     *
     * @param date    当前时间的时间戳
     * @param pattern 格式
     * @return 格式化 yyyy-MM-dd
     */
    public static String format(Object date, String pattern) {
        return format(date, DateFormatEnum.DATE_MEDIUM, pattern);
    }


    /**
     * 日期时间格式化
     *
     * @return 时间格式化 yyyy-MM-dd HH:mm:ss
     */
    public static String formatDateTime() {
        return format(null, DateFormatEnum.DATE_TIME_MEDIUM, null);
    }

    /**
     * 日期时间格式化
     *
     * @return 时间格式化 yyyy-MM-dd HH:mm:ss
     */
    public static String formatDateTime(Object obj) {
        return format(getDate(obj), DateFormatEnum.DATE_TIME_MEDIUM, null);
    }

    /**
     * 获取两个时间段的交集区间 单位分钟
     *
     * @param stime1 时间点1的起始时间
     * @param etime1 时间点1的结束时间
     * @param stime2 时间点2的起始时间
     * @param etime2 时间点2的结束时间
     * @return 间隔分钟
     */
    public static Integer getTimeInterval(Long stime1, Long etime1, Long stime2, Long etime2) {
        int f = 0;

        if (stime1 > etime1 || stime2 > etime2) {
            throw new RuntimeException("起始时间不能大于结束时间");
        }

        if (etime1 <= stime2 || stime1 >= etime2) {
            return f;
        }

        long[] a = {stime1, etime1, stime2, etime2};
        //从小到大排序，取第二、第三计算
        Arrays.sort(a);
        f = Math.toIntExact(a[2] - a[1]);

        return Math.round((f / 60000) * 100) / 100;
    }

    /**
     * 获取时间参数 是否均在指定区间内存在(多个参数 格式为and)
     *
     * @param stime  开始时间
     * @param etime  结束时间
     * @param params 时间参数
     * @return 时间参数是否再区间内
     */
    public static Boolean getTimeExist(Long stime, Long etime, Long... params) {
        for (Long param : params) {
            if (param > stime && param < etime) {
                return true;
            } else {
                return false;
            }
        }
        return true;
    }


    /**
     * yyyy-MM-dd 转时间
     *
     * @param time yyyy-MM-dd
     * @return 字符串转时间
     */
    public static Date parseDate(String time) {
        return parseDate(time, null, "yyyy-MM-dd");
    }

    public static Date parseDate(String time, DateFormatEnum dateFormat) {
        return parseDate(time, dateFormat, null);
    }

    public static Date parseDate(String time, String format) {
        return parseDate(time, null, format);
    }


    /**
     * yyyy-MM-dd 转时间
     *
     * @param str    时间
     * @param format 自定义格式化
     * @return 字符串转时间
     */
    @SneakyThrows
    public static Date parseDate(String str, DateFormatEnum dateFormat, String format) {
        if (StringTool.isBlank(str)) {
            return new Date();
        }
        if (null != dateFormat) {
            format = dateFormat.getDesc();
        }
        return DateUtils.parseDate(str, format);
    }
}
